var Stream = require('stream');
var util = require('util');
var protocol = require('pomelo-protocol');
var Package = protocol.Package;
var logger = require('pomelo-logger').getLogger('pomelo', __filename);

/**
 * Work states
 */
var ST_HEAD = 1;      // wait for head  等待包头
var ST_BODY = 2;      // wait for body  等待包体
var ST_CLOSED = 3;    // closed         关闭

/**
 * Tcp socket wrapper with package compositing.
 * Collect the package from socket and emit a completed package with 'data' event.
 * 从socket中收集一个完整的包并通过data事件发送一个完整的包
 * Uniform with ws.WebSocket interfaces.
 * 同websocket的接口一致
 *
 * @param {Object} socket origin socket from node.js net module
 * @param {Object} opts   options parameter.
 *                        opts.headSize size of package head
 *                        opts.headHandler(headBuffer) handler for package head. caculate and return body size from head data.
 */
var Socket = function(socket, opts) {
  if(!(this instanceof Socket)) {
    return new Socket(socket, opts);
  }

  if(!socket || !opts) {
    throw new Error('invalid socket or opts');
  }

  if(!opts.headSize || typeof opts.headHandler !== 'function') {
    throw new Error('invalid opts.headSize or opts.headHandler');
  }

  // stream style interfaces.  //流式接口
  // TODO: need to port to stream2 after node 0.9
  Stream.call(this);
  this.readable = true;
  this.writeable = true;

  this._socket = socket;//原生socket
  this.headSize = opts.headSize;//包头大小
  this.closeMethod = opts.closeMethod;//关闭方法？
  this.headBuffer = new Buffer(opts.headSize);//包头缓冲区
  this.headHandler = opts.headHandler;//包头处理函数 计算出body大小

  this.headOffset = 0;//包头偏移
  this.packageOffset = 0;//包偏移
  this.packageSize = 0;//包大小
  this.packageBuffer = null;//包缓冲区

  // bind event form the origin socket
  //给原生的socket绑定事件
  this._socket.on('data', ondata.bind(null, this));//数据
  this._socket.on('end', onend.bind(null, this));//结束
  this._socket.on('error', this.emit.bind(this, 'error'));//错误
  this._socket.on('close', this.emit.bind(this, 'close'));//关闭

  this.state = ST_HEAD;//初始状态为等待包头
};

util.inherits(Socket, Stream);

module.exports = Socket;

Socket.prototype.send = function(msg, encode, cb) {//发送数据
  this._socket.write(msg, encode, cb);
};

Socket.prototype.close = function() {//关闭连接
  if(!!this.closeMethod && this.closeMethod === 'end') {//如果定义了关闭方法为end
    this._socket.end();//调用之
  } else {
    try { 
      this._socket.destroy();//否则使用destroy来关闭
    } catch (e) {
      logger.error('socket close with destroy error: %j', e.stack);
    }
  }
};

var ondata = function(socket, chunk) {//数据到来时
  if(socket.state === ST_CLOSED) {
    throw new Error('socket has closed');
  }

  if(typeof chunk !== 'string' && !Buffer.isBuffer(chunk)) {//如果块不是字符串（那就是buffer咯）也不是buffer
    throw new Error('invalid data');//错误数据
  }

  if(typeof chunk === 'string') {//如果是字符串
    chunk = new Buffer(chunk, 'utf8');//分配一块缓冲区
  }

  var offset = 0, end = chunk.length;

  //等待包头 并且包缓冲为空 则 检查包头中的类型是否正确
  if(socket.state === ST_HEAD && socket.packageBuffer === null && !checkTypeData(chunk[0])) {
    logger.error('close the connection with invalid head message, the remote ip is %s && port is %s && message is %j', socket._socket.remoteAddress, socket._socket.remotePort, chunk);
    socket.close();
  }

  while(offset < end) {//循环从数据块中读取出完整的包
    if(socket.state === ST_HEAD) {
      offset = readHead(socket, chunk, offset);
    }

    if(socket.state === ST_BODY) {
      offset = readBody(socket, chunk, offset);
    }
  }

  return true;
};

var onend = function(socket, chunk) {
  if(chunk) {
    socket._socket.write(chunk);
  }

  socket.state = ST_CLOSED;
  reset(socket);
  socket.emit('end');
};

/**
 * Read head segment from data to socket.headBuffer.
 *
 * @param  {Object} socket Socket instance
 * @param  {Object} data   Buffer instance
 * @param  {Number} offset offset read star from data
 * @return {Number}        new offset of data after read
 */
var readHead = function(socket, data, offset) {//读取包头
  var hlen = socket.headSize - socket.headOffset;//包头大小-包头偏移＝剩余包头未读大小（想读取的数据长度）
  var dlen = data.length - offset;//可以被用来读取的数据长度
  var len = Math.min(hlen, dlen);//取小值 代表可以读取的数据的长度
  var dend = offset + len;//读取到data的什么位置处停止 也就是sourceEnd

  //buf.copy(targetBuffer, [targetStart], [sourceStart], [sourceEnd])
  data.copy(socket.headBuffer, socket.headOffset, offset, dend);//拷贝数据到包头缓冲

  socket.headOffset += len;//增加包头偏移 代表包头现在读了多少了

  if(socket.headOffset === socket.headSize) {//如果包头偏移等于包头大小了，代表读取包头完毕
    // if head segment finished
    var size = socket.headHandler(socket.headBuffer);//求出包体大小
    if(size < 0) {//如果小于0，数据包肯定出错了
      throw new Error('invalid body size: ' + size);
    }
    socket.packageSize = size + socket.headSize;//完整的包大小
    socket.packageBuffer = new Buffer(socket.packageSize);//申请缓冲区存放完整包数据
    socket.headBuffer.copy(socket.packageBuffer, 0, 0, socket.headSize);//拷贝包头数据
    socket.packageOffset = socket.headSize;//完整包偏移为包头大小
    socket.state = ST_BODY;//设置状态为等待包体
  }

  return dend;//下次读取偏移
};

/**
 * Read body segment from data buffer to socket.packageBuffer;
 *
 * @param  {Object} socket Socket instance
 * @param  {Object} data   Buffer instance
 * @param  {Number} offset offset read star from data
 * @return {Number}        new offset of data after read
 */
var readBody = function(socket, data, offset) {//读取包体
  var blen = socket.packageSize - socket.packageOffset;//想读取
  var dlen = data.length - offset;//可以读取
  var len = Math.min(blen, dlen);//实际读取
  var dend = offset + len;//读到哪

  data.copy(socket.packageBuffer, socket.packageOffset, offset, dend);//拷贝数据

  socket.packageOffset += len;//更改偏移

  if(socket.packageOffset === socket.packageSize) {//读取一个完整的包了
    // if all the package finished
    var buffer = socket.packageBuffer;
    socket.emit('message', buffer);//将数据发出去 通过message事件
    reset(socket);//清理状态数据
  }

  return dend;//下次读取偏移
};

var reset = function(socket) {
  socket.headOffset = 0;
  socket.packageOffset = 0;
  socket.packageSize = 0;
  socket.packageBuffer = null;
  socket.state = ST_HEAD;
};

var checkTypeData = function(data) {//检查package包的类型
  return data === Package.TYPE_HANDSHAKE || data === Package.TYPE_HANDSHAKE_ACK || data === Package.TYPE_HEARTBEAT || data === Package.TYPE_DATA || data === Package.TYPE_KICK;
};
